package com.meshnetics.gb.stack.mac;

import com.meshnetics.gb.stack.core.event.StackEvent;
import com.meshnetics.gb.stack.mac.primitives.*;
import com.meshnetics.gb.type.impl.ByteUtils;
import com.meshnetics.gb.type.impl.StringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

/**
 * Factory for deserializing of mac primitives from ByteBuffer. Factory takes byte from ByteBuffer considers it as
 * code of primitive and searchs for registered constructor that accepts ByteBuffer and RadioMACStacklayer as arguments.
 *
 * @author AKovylin
 */
class PrimitivesFactory {
    private final Map factories = new HashMap(); // Byte -> Constructor
    private final RadioMACStackLayer layer;

    public PrimitivesFactory(RadioMACStackLayer layer) {
        this.layer = layer;
    }

    void registerFactoryOf(byte code, Class clazz) throws NoSuchMethodException {
        if (!StackEvent.class.isAssignableFrom(clazz)) {
            throw new IllegalArgumentException("clazz is not a StackEvent");
        }
        Constructor con = clazz.getConstructor(new Class[]{ByteBuffer.class, RadioMACStackLayer.class});
        registerFactoryOf(code, con);
    }

    private void registerFactoryOf(byte code, Constructor constructor) {
        Byte key = new Byte(code);
        if (factories.containsKey(key)) {
            throw new IllegalArgumentException("factory for code " + StringUtils.toHexString(code) +
                    " already registered");
        }
        factories.put(key, constructor);
    }

    public StackEvent fromByteBuffer(ByteBuffer bb)
            throws IllegalAccessException, InvocationTargetException, InstantiationException {
        ByteUtils.ensureNotNull(bb);
        ByteUtils.ensureCapacity(bb, 1);
        byte code = bb.get();
        Constructor con = (Constructor) factories.get(new Byte(code));
        if (con == null) {
            throw new IllegalArgumentException("unknown code of command " + StringUtils.toHexString(code));
        }
        return (StackEvent) con.newInstance(new Object[]{bb, layer});
    }

    public static PrimitivesFactory newMACPrimitivesFactory(RadioMACStackLayer layer) throws NoSuchMethodException {
        PrimitivesFactory res = new PrimitivesFactory(layer);
        res.registerFactoryOf(MACResetConfirm.CODE, MACResetConfirm.class);
        res.registerFactoryOf(MACGetConfirm.CODE, MACGetConfirm.class);
        res.registerFactoryOf(MACSetConfirm.CODE, MACSetConfirm.class);
        res.registerFactoryOf(MACRxEnableConfirm.CODE, MACRxEnableConfirm.class);
        res.registerFactoryOf(MACStartConfirm.CODE, MACStartConfirm.class);
        res.registerFactoryOf(MACBeaconNotifyIndication.CODE, MACBeaconNotifyIndication.class);
        res.registerFactoryOf(MACCommStatusIndication.CODE, MACCommStatusIndication.class);
        res.registerFactoryOf(MACScanConfirm.CODE, MACScanConfirm.class);
        res.registerFactoryOf(MACDataConfirm.CODE, MACDataConfirm.class);
        res.registerFactoryOf(MACDataIndication.CODE, MACDataIndication.class);
        res.registerFactoryOf(MACAssociateConfirm.CODE, MACAssociateConfirm.class);
        res.registerFactoryOf(MACAssociateIndication.CODE, MACAssociateIndication.class);
        res.registerFactoryOf(MACPurgeConfirm.CODE, MACPurgeConfirm.class);
        res.registerFactoryOf(MACGtsConfirm.CODE, MACGtsConfirm.class);
        res.registerFactoryOf(MACGtsIndication.CODE, MACGtsIndication.class);
        res.registerFactoryOf(MACPollConfirm.CODE, MACPollConfirm.class);
        res.registerFactoryOf(MACSyncLossIndication.CODE, MACSyncLossIndication.class);
        res.registerFactoryOf(MACOrphanIndication.CODE, MACOrphanIndication.class);
        res.registerFactoryOf(MACDisassociateConfirm.CODE, MACDisassociateConfirm.class);
        res.registerFactoryOf(MACDisassociateIndication.CODE, MACDisassociateIndication.class);
        return res;
    }
}
